package com.zzyl.nursing.job;

import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.zzyl.common.constant.CacheConstants;
import com.zzyl.nursing.domain.*;
import com.zzyl.nursing.mapper.AlertDataMapper;
import com.zzyl.nursing.mapper.NursingElderMapper;
import com.zzyl.nursing.service.IAlertDataService;
import com.zzyl.nursing.service.IAlertRuleService;
import com.zzyl.nursing.vo.AlertNotifyVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import java.time.LocalTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Component
@Slf4j
public class DeviceAlertJob {

    @Autowired
    private IAlertRuleService alertRuleService;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private NursingElderMapper nursingElderMapper;

    @Autowired
    private IAlertDataService alertDataService;

    /**
     *  业务流程
     *  1.查询所有的报警规则
     *  2.根据报警规则查询最近的报警数据（Redis）
     *  3.检验报警数据是否满足报警规则
     *  4.将报警数据插入到报警数据表中，并通知相关人员进行处理
     */
    public void deviceDataAlertFilter(){
        //1.拿到所有的报警规则数据
        List<AlertRule> alertRules = alertRuleService.list(Wrappers.lambdaQuery(AlertRule.class)
                .eq(AlertRule::getStatus, AlertRule.STATUS_ENABLE));
        if(CollectionUtils.isEmpty(alertRules)){//规则为空，结束
            log.info("报警规则启用数量为0");
            return;
        }

        //2.从redis中拿到所有设备数据
        List<Object> objectList = redisTemplate.opsForHash().values(CacheConstants.IOT_DEVICE_LAST_DATA);//拿到所有的设备数据
        List<DeviceData> allDeviceDataList = new ArrayList<>();
        for (Object object : objectList) {
            allDeviceDataList.addAll(JSONUtil.toList(object.toString(),DeviceData.class));
        }
        if(CollectionUtils.isEmpty(allDeviceDataList)){
            log.info("报警数据为空");
            return;
        }

        //3.检验报警数据是否满足报警规则
        for (AlertRule alertRule : alertRules) {
            //判断数据上报时间是否在时间段内：（对所有设备数据过滤，条件为：当前产品、物模型、报警规则生效时间段）
            List<DeviceData> filteredDeviceData = filterDeviceData(alertRule, allDeviceDataList);
            if (CollectionUtils.isEmpty(filteredDeviceData)){
                log.info("当前规则下，生效时间内无设备数据");
                continue;//退出当前循环
            }


            for (DeviceData deviceData : filteredDeviceData) {
                //定义异常数据累计：key为iot:exception:iotid+functionid，value为统计次数
                String exceptionKey = CacheConstants.IOT_EXCEPTION_DATA + deviceData.getIotId() + deviceData.getFunctionId();
                //定义沉默时间：key为iot:silent:iot+functionid,value设置为""，过期时间为周期时间
                String silentKey = CacheConstants.IOT_SILENT_PERIOD + deviceData.getIotId() + deviceData.getFunctionId();

                //判断采集的单条数据是否满足规则配置的值
                boolean exception = checkExceptionData(deviceData,alertRule);
                if (!exception){//设备数据正常，重置累计次数
                    log.info("此设备数据正常");
                    redisTemplate.delete(exceptionKey);
                    continue;
                }

                //如果数据异常，查询redis中是否已经累计，没有则创建key并置为1，有则数据+1
                Object object= redisTemplate.opsForValue().get(exceptionKey);
                int exceptionTimes = ObjectUtils.isEmpty(object)?1:(Integer)object+1;
                redisTemplate.opsForValue().set(exceptionKey,exceptionTimes);//累加次数加入缓存
                if(exceptionTimes < alertRule.getAlertSilentPeriod()){
                    log.info("设备连续异常{}次",exceptionTimes);
                    //判断设备是否处于沉默周期，
                    Object silent = redisTemplate.opsForValue().get(silentKey);
                    if (!ObjectUtils.isEmpty(silent)){//如果不为空，说明当前处于沉默周期,删除redis中设备异常次数
                        log.info("当前处于沉默周期");
                        redisTemplate.delete(exceptionKey);
                    }
                    continue;
                }
                //4.将报警数据插入到报警数据表中，并通知相关人员进行处理
                saveAlertData(alertRule,deviceData);
                redisTemplate.delete(exceptionKey);
                redisTemplate.opsForValue().set(silentKey,"",alertRule.getAlertSilentPeriod(), TimeUnit.MINUTES);
            }
        }
    }

    /**
     * 通过productkey、functionid、生效时间段过滤集合
     * @param alertRule
     * @param allDeviceDataList
     * @return List<DeviceData>
     */
    private static List<DeviceData> filterDeviceData(AlertRule alertRule, List<DeviceData> allDeviceDataList) {
        String alertEffectivePeriod = alertRule.getAlertEffectivePeriod();
        String[] strs = alertEffectivePeriod.split("~");
        LocalTime begin = LocalTime.parse(strs[0]);
        LocalTime end = LocalTime.parse(strs[1]);
        return allDeviceDataList.stream()
                .filter(deviceData ->
                   deviceData.getProductKey().equals(alertRule.getProductKey())
                            && deviceData.getAlarmTime().toLocalTime().isAfter(begin)
                            && deviceData.getAlarmTime().toLocalTime().isBefore(end)
                            && deviceData.getFunctionId().equals(alertRule.getFunctionId())
                ).collect(Collectors.toList());
    }

    /**
     * 保存连续三次报警的设备
     * @param alertRule
     * @param deviceData
     */
    private void saveAlertData(AlertRule alertRule, DeviceData deviceData) {
        //定义批量插入
        List<AlertData> alertDataList = new ArrayList<>();
        List<Long> userIds = new ArrayList<>();//通知接收人
        //封装相关数据
        AlertData alertData = new AlertData();
        BeanUtils.copyProperties(deviceData,alertData);
        alertData.setId(null);
        alertData.setAlertRuleId(alertRule.getId());
        alertData.setAlertReason(alertRule.getFunctionName()+alertRule.getOperator()+alertRule.getValue()+alertRule.getDuration()+alertRule.getAlertEffectivePeriod());
        alertData.setStatus(AlertData.NO_PROCESSED);
        alertData.setType(alertRule.getAlertDataType());
        alertData.setUpdateTime(new Date());
        //查找接收人id
        if (Device.PORTABLE.equals(alertData.getLocationType())){//如果是随身设备通知护工
            //通过位置类型为0，绑定的是老人，通过老人id找到护理员,并封装到数据中列表中
            List<Long> nursingElderIds = nursingElderMapper.getNursingElderIdByElderId(Long.valueOf(alertData.getAccessLocation()));
            nursingElderIds.forEach(nursingElderId->{
                alertData.setUserId(nursingElderId);
                userIds.add(nursingElderId);
                alertDataList.add(alertData);
            });
        }else {//如果是固定设备
            if (Device.LOCALTION_BED.equals(alertData.getAccessLocation())) {//固定位置是床
                //固定设备+物理位置类型为床->通过床找到老人id->通过老人id找到护工id
                String accessLocaltion = alertData.getAccessLocation();
                String bedNumber = accessLocaltion.substring(accessLocaltion.lastIndexOf(","));
                List<Long> nursingElderIds = nursingElderMapper.getNursingElderIdByBedNumber(bedNumber);
                nursingElderIds.forEach(nursingElderId -> {
                    alertData.setUserId(nursingElderId);
                    userIds.add(nursingElderId);
                    alertDataList.add(alertData);
                });
            }else{
                //固定设备+其他物理位置类型->通知维修工,这里固定写为小张
                alertData.setUserId(104L);
                userIds.add(104L);
                alertDataList.add(alertData);
            }
        }
        //封装接收人数据
        AlertNotifyVo alertNotifyVo = new AlertNotifyVo();
        BeanUtils.copyProperties(alertData,alertNotifyVo);
        alertNotifyVo.setNotifyType(AlertNotifyVo.ALERT);
        alertNotifyVo.setFunctionName(alertRule.getFunctionName());
        //批量保存报警数据和通知接收人
        if(!CollectionUtils.isEmpty(alertDataList)){
            alertDataService.batchInsertAlertData(alertDataList,alertNotifyVo,userIds);
        }
    }

    /**
     * 检查特定产品、特定功能的数据值时是否在阈值中
     * @param deviceData
     * @param alertRule
     * @return 是否为异常值（不再阈值中）
     */
    private boolean checkExceptionData(DeviceData deviceData, AlertRule alertRule) {
        Double value = alertRule.getValue();
        String operator = alertRule.getOperator();
        double dataValue = Double.parseDouble(deviceData.getDataValue());
        if (">".equals(operator)){
            return dataValue>value;
        }else if ("<".equals(operator)){
            return dataValue<value;
        }else {//暂未开发其他判断
            return false;
        }
    }
}
